package fi.internetix.edelphi.liveillustrator;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.UserTransaction;

import org.apache.commons.lang.math.NumberUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import fi.internetix.edelphi.dao.GenericDAO;
import fi.internetix.edelphi.dao.panels.PanelDAO;
import fi.internetix.edelphi.dao.querydata.QueryQuestionOptionAnswerDAO;
import fi.internetix.edelphi.dao.querydata.QueryReplyDAO;
import fi.internetix.edelphi.dao.querylayout.QueryPageDAO;
import fi.internetix.edelphi.dao.querylayout.QueryPageSettingDAO;
import fi.internetix.edelphi.dao.querylayout.QueryPageSettingKeyDAO;
import fi.internetix.edelphi.dao.querymeta.QueryFieldDAO;
import fi.internetix.edelphi.dao.querymeta.QueryOptionFieldOptionDAO;
import fi.internetix.edelphi.dao.resources.QueryDAO;
import fi.internetix.edelphi.dao.users.UserDAO;
import fi.internetix.edelphi.dao.users.UserPictureDAO;
import fi.internetix.edelphi.domainmodel.panels.Panel;
import fi.internetix.edelphi.domainmodel.querydata.QueryQuestionOptionAnswer;
import fi.internetix.edelphi.domainmodel.querydata.QueryReply;
import fi.internetix.edelphi.domainmodel.querylayout.QueryPage;
import fi.internetix.edelphi.domainmodel.querylayout.QueryPageSetting;
import fi.internetix.edelphi.domainmodel.querylayout.QueryPageSettingKey;
import fi.internetix.edelphi.domainmodel.querylayout.QueryPageType;
import fi.internetix.edelphi.domainmodel.querymeta.QueryOptionField;
import fi.internetix.edelphi.domainmodel.querymeta.QueryOptionFieldOption;
import fi.internetix.edelphi.domainmodel.resources.Folder;
import fi.internetix.edelphi.domainmodel.resources.Query;
import fi.internetix.edelphi.domainmodel.resources.Resource;
import fi.internetix.edelphi.domainmodel.users.User;
import fi.internetix.edelphi.domainmodel.users.UserPicture;

public class Application extends HttpServlet {

  private static final Long QUERY_ID = 381l;
      
  private static final long serialVersionUID = 1L;
  private static final Logger LOGGER = Logger.getLogger(Application.class.getName());

  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Action action = Action.valueOf(request.getParameter("action"));

    GenericDAO.setEntityManager(entityManager);
    try {
      try {
        userTransaction.begin();
      } catch (Exception e) {
        LOGGER.log(Level.SEVERE, "Could not start transaction", e);
        throw new ServletException(e);
      }
  
      try {
        switch (action) {
          case UPDATE:
            handleUpdate(request, response);
          break;
          case QUERY_META:
            handleQueryMeta(request, response);
          break;
          case FACE:
            handleFace(request, response);
          break;
          case CONTROL:
            handleControl(request, response);
          break;
        }
  
        userTransaction.commit();
      } catch (Throwable e) {
        try {
          userTransaction.rollback();
        } catch (Exception e1) {
          LOGGER.log(Level.SEVERE, "Could not rollback transaction", e1);
          throw new ServletException(e);
        }
      }
    } finally {
      GenericDAO.setEntityManager(null);
    }
  }

  private void handleUpdate(HttpServletRequest request, HttpServletResponse response) throws JSONException, IOException {
    QueryDAO queryDAO = new QueryDAO();
    UserDAO userDAO = new UserDAO();
    QueryPageDAO queryPageDAO = new QueryPageDAO();
    QueryReplyDAO queryReplyDAO = new QueryReplyDAO();
    QueryFieldDAO queryFieldDAO = new QueryFieldDAO();
    QueryQuestionOptionAnswerDAO queryQuestionOptionAnswerDAO = new QueryQuestionOptionAnswerDAO();
    QueryOptionFieldOptionDAO queryOptionFieldOptionDAO = new QueryOptionFieldOptionDAO();
    
    JSONObject result = new JSONObject();
    
    Long random = NumberUtils.createLong(request.getParameter("random"));
    String pageParam = request.getParameter("page");
    
    if ("INDEX".equals(pageParam)) {
      
    } else {
      Integer pageNumber = NumberUtils.createInteger(pageParam);
      String[] unanonymousUsersParam = request.getParameterValues("unanonymousUsers");
      int i = 0;
      Long[] unanonymousUsers = new Long[unanonymousUsersParam.length];
      for (String unanonymousUserParam : unanonymousUsersParam) {
        unanonymousUsers[i++] = NumberUtils.createLong(unanonymousUserParam);
      }

      Query query = queryDAO.findById(QUERY_ID);
      Panel panel = getResourcePanel(query);
      QueryPage queryPage = queryPageDAO.findByQueryAndPageNumber(query, pageNumber);
      List<QueryReply> queryReplies = queryReplyDAO.listByQueryAndArchivedAndStamp(query, Boolean.FALSE, panel.getCurrentStamp());
      
      String fieldBaseName = "scale2d.";
      String fieldNameX = fieldBaseName + "x";
      String fieldNameY = fieldBaseName + "y";
      
      QueryOptionField queryFieldX = (QueryOptionField) queryFieldDAO.findByQueryPageAndName(queryPage, fieldNameX);
      QueryOptionField queryFieldY = (QueryOptionField) queryFieldDAO.findByQueryPageAndName(queryPage, fieldNameY);
      List<QueryOptionFieldOption> optionsX = queryOptionFieldOptionDAO.listByQueryField(queryFieldX);
      List<QueryOptionFieldOption> optionsY = queryOptionFieldOptionDAO.listByQueryField(queryFieldY);
      
      int maxX = 0;
      int maxY = 0;
      
      for (QueryOptionFieldOption optionX : optionsX) {
        int x = NumberUtils.createInteger(optionX.getValue());
        maxX = Math.max(maxX, x);
      }

      for (QueryOptionFieldOption optionY : optionsY) {
        int y = NumberUtils.createInteger(optionY.getValue());
        maxY = Math.max(maxY, y);
      }
      
      maxX++;
      maxY++;
      
      Double[][] values = new Double[maxX][];
      for (int x = 0; x < maxX; x++) {
        values[x] = new Double[maxY];
      }
      
      if (random != null) {
        for (int x = 0; x < maxX; x++) {
          for (int y = 0; y < maxY; y++) {
            values[x][y] = Math.random() * random;  
          }
        }
      } else {
        for (QueryReply queryReply : queryReplies) {
          QueryQuestionOptionAnswer answerX = queryQuestionOptionAnswerDAO.findByQueryReplyAndQueryField(queryReply, queryFieldX);
          QueryQuestionOptionAnswer answerY = queryQuestionOptionAnswerDAO.findByQueryReplyAndQueryField(queryReply, queryFieldY);
          
          if (answerX != null && answerY != null) {
            int x = NumberUtils.createInteger(answerX.getOption().getValue());
            int y = NumberUtils.createInteger(answerY.getOption().getValue());
            
            values[x][y] = new Double(values[x][y] != null ? values[x][y] + 1 : 1); 
          }
        }
      }
      
      JSONArray valuesJson = new JSONArray();
      
      for (int x = 0; x < maxX; x++) {
        for (int y = 0; y < maxY; y++) {
          JSONObject valueJson = new JSONObject();
          valueJson.put("x", x);
          valueJson.put("y", y);
          valueJson.put("value", values[x][y] != null ? values[x][y] : 0);
          valuesJson.put(valueJson);
        }
      }
      
      result.put("values", valuesJson);
      
      JSONArray unanonymousValues = new JSONArray();
      
      for (Long unanonymousUser : unanonymousUsers) {
        JSONObject unanonymousValue = new JSONObject();
        
        if (random != null) {
          unanonymousValue.put("x", Math.round(Math.random() * (maxX - 1)));
          unanonymousValue.put("y", Math.round(Math.random() * (maxY - 1)));
          unanonymousValue.put("userId", unanonymousUser);
          unanonymousValue.put("userName", "Jamppa " + unanonymousUser);
        } else {
          User user = userDAO.findById(unanonymousUser);
          QueryReply queryReply = queryReplyDAO.findByUserAndQueryAndStamp(user, query, panel.getCurrentStamp());
          if (queryReply != null) {
            QueryQuestionOptionAnswer answerX = queryQuestionOptionAnswerDAO.findByQueryReplyAndQueryField(queryReply, queryFieldX);
            QueryQuestionOptionAnswer answerY = queryQuestionOptionAnswerDAO.findByQueryReplyAndQueryField(queryReply, queryFieldY);
            
            if (answerX != null && answerY != null) {
              int x = NumberUtils.createInteger(answerX.getOption().getValue());
              int y = NumberUtils.createInteger(answerY.getOption().getValue());
              
              unanonymousValue.put("x", x);
              unanonymousValue.put("y", y);
              unanonymousValue.put("userId", unanonymousUser);
              unanonymousValue.put("userName", user.getLastName());
              
            }
          }
        }
        
        unanonymousValues.put(unanonymousValue);
      }
      
      result.put("unanonymousValues", unanonymousValues);      
    }
    
    try {
      List<ControlCommand> commands = Utils.retrieveControlCommands();
      JSONArray commandsJson = new JSONArray();
      for (ControlCommand command : commands) {
        JSONObject commandJson = new JSONObject();
        commandJson.put("id", command.getId());
        commandJson.put("action", command.getAction());
        commandJson.put("args", command.getArgs());
        commandsJson.put(commandJson);
      }
      
      result.put("commands", commandsJson);
    } catch (SQLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    
//    response.setContentType("text/json");
    
    PrintWriter writer = response.getWriter();
    writer.print(result.toString(0));
    writer.flush();
    writer.close();
  }
  


  private void handleQueryMeta(HttpServletRequest request, HttpServletResponse response) throws JSONException, IOException {
    QueryDAO queryDAO = new QueryDAO();
    QueryPageDAO queryPageDAO = new QueryPageDAO();
    QueryFieldDAO queryFieldDAO = new QueryFieldDAO();
    QueryOptionFieldOptionDAO queryOptionFieldOptionDAO = new QueryOptionFieldOptionDAO();
    
    Query query = queryDAO.findById(QUERY_ID);
    String fieldBaseName = "scale2d.";
    String fieldNameX = fieldBaseName + "x";
    String fieldNameY = fieldBaseName + "y";
    
    JSONObject queryJson = new JSONObject();
    queryJson.put("name", query.getName());

    JSONArray pagesJson = new JSONArray();
    List<QueryPage> queryPages = queryPageDAO.listByQuery(query);
    Collections.sort(queryPages, new Comparator<QueryPage>() {
      @Override
      public int compare(QueryPage o1, QueryPage o2) {
        return o1.getPageNumber().compareTo(o2.getPageNumber());
      }
    });
    
    for (QueryPage queryPage : queryPages) {
    
      JSONObject pageJson = new JSONObject();
      
      if (queryPage.getPageType() == QueryPageType.THESIS_SCALE_2D) {
        QueryOptionField queryFieldX = (QueryOptionField) queryFieldDAO.findByQueryPageAndName(queryPage, fieldNameX);
        QueryOptionField queryFieldY = (QueryOptionField) queryFieldDAO.findByQueryPageAndName(queryPage, fieldNameY);
        List<QueryOptionFieldOption> optionsX = queryOptionFieldOptionDAO.listByQueryField(queryFieldX);
        List<QueryOptionFieldOption> optionsY = queryOptionFieldOptionDAO.listByQueryField(queryFieldY);
        
        int maxX = 0;
        int maxY = 0;
        
        List<String> xTickLabels = new ArrayList<String>();
        
        for (QueryOptionFieldOption optionX : optionsX) {
          int x = NumberUtils.createInteger(optionX.getValue());
          maxX = Math.max(maxX, x);
          xTickLabels.add(optionX.getText());
        }
  
        List<String> yTickLabels = new ArrayList<String>();
        for (QueryOptionFieldOption optionY : optionsY) {
          int y = NumberUtils.createInteger(optionY.getValue());
          maxY = Math.max(maxY, y);
          yTickLabels.add(optionY.getText());
        }
        
        maxX++;
        maxY++;
        
        pageJson.put("xAxisLabel", queryFieldX.getCaption());
        pageJson.put("yAxisLabel", queryFieldY.getCaption());
        pageJson.put("xValueCount", String.valueOf(maxX));
        pageJson.put("yValueCount", String.valueOf(maxY));
        
        for (int i = 0, l = xTickLabels.size(); i < l; i++) {
          pageJson.put("xTickLabel." + i, xTickLabels.get(i)); 
        }
        
        for (int i = 0, l = yTickLabels.size(); i < l; i++) {
          pageJson.put("yTickLabel." + i, yTickLabels.get(i)); 
        }
      }
      
      pageJson.put("title", queryPage.getTitle());
      pageJson.put("type", queryPage.getPageType());
      pageJson.put("number", queryPage.getPageNumber());
      pageJson.put("thesis", getQueryPageOption(queryPage, "thesis.text"));
      pageJson.put("description", getQueryPageOption(queryPage, "thesis.description"));
      
     
      pagesJson.put(pageJson);
    }
    
    queryJson.put("pages", pagesJson);
    
    response.setCharacterEncoding("UTF-8");
//    response.setContentType("text/json");
    
    PrintWriter writer = response.getWriter();
    writer.print(queryJson.toString(0));
    writer.flush();
    writer.close();
  }
  
  private String getQueryPageOption(QueryPage queryPage, String queryPaeOption) {
    QueryPageSettingKeyDAO queryPageSettingKeyDAO = new QueryPageSettingKeyDAO();
    QueryPageSettingDAO queryPageSettingDAO = new QueryPageSettingDAO();
    QueryPageSettingKey key = queryPageSettingKeyDAO.findByName(queryPaeOption);
    if (key != null) {
      QueryPageSetting setting = queryPageSettingDAO.findByKeyAndQueryPage(key, queryPage);
      if (setting != null)
        return setting.getValue();
    }
    
    return null;
  }

  private void handleFace(HttpServletRequest request, HttpServletResponse response) throws IOException {
    UserDAO userDAO = new UserDAO();
    UserPictureDAO userPictureDAO = new UserPictureDAO();
    
    Long userId = NumberUtils.createLong(request.getParameter("userId"));
    User user = userDAO.findById(userId);
    UserPicture userPicture = userPictureDAO.findByUser(user);
    if (userPicture != null) {
      response.setContentType(userPicture.getContentType());
      ServletOutputStream outputStream = response.getOutputStream();
      outputStream.write(userPicture.getData());
      outputStream.flush();
      outputStream.close();
    }
  }

  private void handleControl(HttpServletRequest request, HttpServletResponse response) {
    try {
      Utils.sendControlCommand(request.getParameter("ctrlaction"), request.getParameter("args"));
    } catch (SQLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  
  private Panel getResourcePanel(Resource resource) {
    PanelDAO panelDAO = new PanelDAO();
    
    List<Folder> resourceFolders = new ArrayList<Folder>();
    Resource current = resource;
    Folder folder = current instanceof Folder ? (Folder) current : current.getParentFolder();
    while (folder != null) {
      resourceFolders.add(folder);
      current = folder;
      folder = current.getParentFolder();
    }
    
    Folder panelFolder = null;
    int panelIndex = resourceFolders.size() - 2;

    if (panelIndex >= 0) {
      panelFolder = resourceFolders.get(panelIndex);
    }
   
    if (panelFolder != null) {
      return panelDAO.findByRootFolder(panelFolder);
    }
    
    return null;
  }
  
  @PersistenceContext
  private EntityManager entityManager;
  
  @javax.annotation.Resource
  private UserTransaction userTransaction;
}